package networkInterface;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

import protocol.Message;

/**
 * Classe ChatNI qui accede au reseau, c'est elle qui permet le transfert des
 * paquets du reseau a la classe ChatController
 * 
 * @author Flo
 * 
 */
public class ChatNI {
	/**
	 * Booleen BAVARD pour tester et ecrire des commentaires dans la console
	 */
	private final static boolean BAVARD = true;
	/**
	 * le socket de Reception
	 */
	private DatagramSocket socketReception;
	/**
	 * le socket d'Emission
	 */
	private DatagramSocket socketEmission;
	/**
	 * La liste des messages recus mais pas encore traites par le controleur
	 */
	private ArrayList<Message> messageEnAttente;

	/**
	 * getter qui retourne les messageEnAttente
	 * 
	 * @return la liste des messages en attente
	 */
	public ArrayList<Message> getMessageEnAttente() {
		return messageEnAttente;
	}

	/**
	 * Constructeur de la classe qui mets les sockets a null
	 */
	public ChatNI() {
		this.socketReception = null;
		this.socketEmission = null;
	}

	/**
	 * connexion au reseau sur les bons ports et initialisation de la liste des
	 * messageEnAttente
	 * 
	 * @param port
	 *            le port sur lequel on va ecouter
	 * @return true si ca a bien marche
	 */
	public boolean connecter(int port) {

		// Creation du socket pour recevoir la communication
		try {
			this.socketReception = new DatagramSocket(port);
			this.socketEmission = new DatagramSocket();
			this.messageEnAttente = new ArrayList<Message>();
		} catch (SocketException e) {
			System.err
					.println("       ChatNI -- Impossible de créer le socket, la connexion est annulée");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * se deconnecter, ferme les sockets et vide la liste des messages
	 */
	public void deconnecter() {
		this.socketReception.close();
		this.socketEmission.close();
		this.messageEnAttente.clear();
	}

	/**
	 * recevoir un paquet par le reseau
	 * 
	 * @throws Exception
	 *             si le socket a ete detruit entre temps ou si c'est notre
	 *             adresse
	 */
	public void recevoir() throws Exception {

		// ************************************
		// Reception et ouverture du paquet
		// ************************************

		// Preparation d'un tableau d'octets pour recevoir le contenu du paquet
		byte[] buf = new byte[5000];

		// Creation du paquet
		DatagramPacket packet = new DatagramPacket(buf, buf.length);

		// Reception du paquet (remplissage)
		try {
			this.socketReception.receive(packet);
		} catch (IOException e) {
			// Si la reception echoue, c'est que le socket a ete detruit entre
			// temps ; on sort sans faire d'histoire
			return;
		}

		if (packet.getAddress().equals(InetAddress.getLocalHost())) {
			return;
		}

		if (ChatNI.BAVARD)
			System.out
					.println("       ChatNI -- Réception d'un paquet sur le port "
							+ socketReception.getLocalPort());

		Message message = deserialiser(buf);
		this.messageEnAttente.add(message);
	}

	/**
	 * envoi d'un message
	 * 
	 * @param h
	 *            Message a envoyer
	 * @param address
	 *            adresse du destinataire
	 * @param port
	 *            d'emission
	 */
	public void envoyer(Message h, InetAddress address, int port) {
		if (ChatNI.BAVARD)
			System.out.println("       ChatNI -- Tentative d'envoi du message "
					+ h.toString() + " vers " + address + ":" + port);

		byte[] buf = serialiser(h);

		// On cree le paquet
		DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
				port);

		// On envoie le paquet
		try {
			this.socketEmission.send(packet);
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (ChatNI.BAVARD)
			System.out
					.println("       ChatNI -- Envoi du message par le socket "
							+ this.socketEmission.getLocalPort());
	}

	/**
	 * Permet de Serialiser un objet de type Message
	 * 
	 * @return un tableau de byte
	 */
	public byte[] serialiser(Message message) {
		byte[] result = null;

		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			// serialisation : ecriture de l'objet dans le flux de sortie
			oos.writeObject(message);
			// on vide le tampon
			oos.flush();
			System.out.println(message.toString() + " a ete serialise");
			result = bos.toByteArray();
		} catch (IOException ioe) {
			ioe.printStackTrace();
			System.out.println("PROBLEME methode write:" + message
					+ "n'a pas ete serialise");
		}
		return result;
	}

	/**
	 * deserialisation d'un paquet
	 * 
	 * @param messToRead
	 *            le message a deserializer
	 * @return un objet de type Message
	 */
	public static Message deserialiser(byte[] messToRead) {
		Message result = null;

		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(messToRead);
			ObjectInputStream ois = new ObjectInputStream(bis);
			result = (Message) ois.readObject();

		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Objet non deserialise");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			System.out.println("Objet non deserialise");
		}

		return result;

	}

	/**
	 * declenche le thread d'envoi du fichier
	 * 
	 * @param adresse
	 *            du destinataire
	 * @param fichier
	 *            a envoyer
	 */
	public void envoyerFichierParTCP(InetAddress adresse, File fichier) {
		ThreadEnvoiFichier threadEnvoiFichier = new ThreadEnvoiFichier(fichier,
				adresse);
		threadEnvoiFichier.start();
	}

	/**
	 * declenche thread reception du fichier
	 * 
	 * @param fichier
	 *            le fichier virtuel contenant le futur chemin du fichier
	 */
	public void recevoirFichierParTCP(File fichier) {
		ThreadReceptionFichier threadReceptionFichier = new ThreadReceptionFichier(
				fichier);
		threadReceptionFichier.start();
	}

	/**
	 * Thread d'envoi du fichier
	 * 
	 * @author Flo
	 * 
	 */
	private class ThreadEnvoiFichier extends Thread {
		/**
		 * Fichier a envoyer
		 */
		private File monFichier;
		/**
		 * adresse du destinataire
		 */
		private InetAddress adresseDest;

		/**
		 * Constructeur
		 * 
		 * @param fichier
		 *            a envoyer
		 * @param adresse
		 *            du destinataire
		 */
		public ThreadEnvoiFichier(File fichier, InetAddress adresse) {
			super();
			this.monFichier = fichier;
			this.adresseDest = adresse;
		}

		/**
		 * L'emetteur est client de la connextion tcp, envoi du fichier par le
		 * tcp de java
		 */
		@Override
		public void run() {
			try {
				// On se connecte au serveur
				Socket skt = new Socket(this.adresseDest, 2234);

				// L'input vient du fichier, l'output est sur le socket
				OutputStream outStream = skt.getOutputStream();
				InputStream inStream = new FileInputStream(this.monFichier);

				// Copie par paquets de 1024 octets
				byte buf[] = new byte[1024];
				int n;
				while ((n = inStream.read(buf)) != -1) {
					outStream.write(buf, 0, n);
				}
				inStream.close();
				outStream.close();

				skt.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Thread de reception du fichier
	 * 
	 * @author Flo
	 * 
	 */
	private class ThreadReceptionFichier extends Thread {

		/**
		 * le fichier virtuel contenant le futur chemin du fichier
		 */
		private File sauvegarde;

		/**
		 * Constructeur
		 * 
		 * @param fichier
		 *            a remplir par le fichier qu'on recoit
		 */
		public ThreadReceptionFichier(File fichier) {
			super();
			this.sauvegarde = fichier;
		}

		/**
		 * Le recepteur est serveur de la connextion tcp, reception du fichier
		 * par le tcp de java
		 */
		@Override
		public void run() {
			try {
				// On cree un serveur, on attend une connexion
				ServerSocket srvr = new ServerSocket(2234);
				Socket skt = srvr.accept();

				// L'input vient du socket, l'output est sur le fichier
				InputStream inStream = skt.getInputStream();
				OutputStream outStream = new FileOutputStream(this.sauvegarde);

				// Copie par paquets de 1024 octets
				byte buf[] = new byte[1024];
				int n;
				while ((n = inStream.read(buf)) != -1) {
					outStream.write(buf, 0, n);
				}
				inStream.close();
				outStream.close();

				skt.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
